package com.duqc.freemarker;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.CaseUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.duqc.freemarker.MysqlType.getDataTypeNameByTypeName;
import static com.duqc.freemarker.MysqlType.getPackageNameByTypeName;
import static java.sql.DriverManager.getConnection;

/**
 * @ClassName CodeGenerator
 * @Description freemarker代码生成
 * @Author duqc
 * @Date 2021/3/1
 **/
@Slf4j
public class CodeGenerator {

    /**
     * 代码根路径
     */
    private static final String PATH = System.getProperty("user.dir");
    /**
     * 配置文件
     */
    private static final String CONFIGURATION_FILE = "application";
    /**
     * 数据库地址
     */
    private static final String DATASOURCE_URL = "freemarker.datasource.url";
    /**
     * 数据库账号
     */
    private static final String DATASOURCE_USERNAME = "freemarker.datasource.username";
    /**
     * 数据库密码
     */
    private static final String DATASOURCE_PASSWORD = "freemarker.datasource.password";

    private void generator(String tableName, String tableDesc) {
        log.info("---------------------开始生成代码---------------------");
        ResourceBundle resource = ResourceBundle.getBundle(CONFIGURATION_FILE);
        String url = resource.getString(DATASOURCE_URL);
        String userName = resource.getString(DATASOURCE_USERNAME);
        String password = resource.getString(DATASOURCE_PASSWORD);
        //获取数据库连接
        try (Connection connection = getConnection(url, userName, password)) {
            DatabaseMetaData metaData = connection.getMetaData();
            ResultSet resultSet = metaData.getColumns(null, null, tableName, "%");
            log.info("---------------------获取数据库连接成功---------------------");
            //保存数据库表所有字段的属性
            List<Attribute> attributes = new ArrayList<>();
            //保存字段涉及的包名
            Set<String> set = new HashSet<>();
            //保存所有字段原始信息
            List<Attribute> fieldList = new ArrayList<>();
            while (resultSet.next()) {
                //Mysql字段类型
                String typeName = resultSet.getString("TYPE_NAME");
                //字段名称
                String columnName = resultSet.getString("COLUMN_NAME");
                //字段注释
                String remarks = resultSet.getString("REMARKS");
                //Java字段类型
                String dataTypeName = getDataTypeNameByTypeName(typeName);
                //Java包名
                set.add(getPackageNameByTypeName(typeName));
                //字段转换成驼峰结构(字符串带'_'的转为驼峰接口，不带'_'不进行操作)
                String toCamelCaseName = columnName;
                if (columnName.contains("_")) {
                    toCamelCaseName = CaseUtils.toCamelCase(columnName, false, '_');
                } else {
                    boolean acronym = isAcronym(columnName);
                    if (acronym) {
                        //如果字段全是大写，字段转为小写
                        toCamelCaseName = columnName.toLowerCase();
                    } else {
                        //字符串第一个字符转为小写
                        toCamelCaseName = lowerFirstChar(columnName);
                    }
                }
                attributes.add(new Attribute(dataTypeName, toCamelCaseName, remarks, columnName));
                //保存所有字段原始信息
                Attribute attribute = new Attribute();
                attribute.setColumnName(columnName);
                attribute.setColumnType(typeName);
                attribute.setName(toCamelCaseName);
                fieldList.add(attribute);
            }
            log.info("---------------------开始执行创建代码流程---------------------");
            //第一步：创建一个Configuration对象，直接new一个对象。构造方法的参数就是freemarker对于的版本号。
            Configuration configuration = new Configuration(Configuration.getVersion());
            // 第二步：设置模板文件所在的路径。
            configuration.setDirectoryForTemplateLoading(new File(String.format("%s\\src\\main\\resources\\module", PATH)));
            // 第三步：设置模板文件使用的字符集。一般就是utf-8
            configuration.setDefaultEncoding("UTF-8");
            //设置模板异常处理程序
            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            // 第四步：加载一个模板，创建一个模板对象
            Template model = configuration.getTemplate("Model.ftl");
            Template controller = configuration.getTemplate("Controller.ftl");
            Template service = configuration.getTemplate("Service.ftl");
            Template serviceImpl = configuration.getTemplate("ServiceImpl.ftl");
            Template dao = configuration.getTemplate("Dao.ftl");
            Template mapper = configuration.getTemplate("Mapper.ftl");
            Template dto = configuration.getTemplate("Dto.ftl");

            //类名（先将表名称转换为驼峰结构，再将转换后的字符串的首字母大写）
            String lowClassName = CaseUtils.toCamelCase(tableName, false, '_');
            String className = largerFirstChar(lowClassName);
            //五步：创建一个模板使用的数据集，可以是pojo也可以是map。一般是Map。
            Map<String, Object> map = new HashMap<>();
            map.put("className", className);
            map.put("lowClassName", lowClassName);
            map.put("author", InetAddress.getLocalHost().getHostName());
            map.put("time", new SimpleDateFormat("yyyy-M-d").format(Calendar.getInstance().getTime()));
            map.put("tableDesc", tableDesc == null ? className : tableDesc);
            map.put("attrs", attributes);
            map.put("packageNameSet", set);
            map.put("fieldList", fieldList);
            map.put("tableName", tableName);

            //包名
            String name = this.getClass().getPackage().getName();
            map.put("packageUri", name.substring(0, name.lastIndexOf(".")));

            String codePath = String.format("%s\\src\\main\\java\\%s\\web", PATH,
                    name.substring(0, name.lastIndexOf(".")).replace(".", "\\"));
            String xmlPath = PATH + "\\src\\main\\resources\\com\\duqc\\mapper";
            //生成模板（六、七、八）
            templateProcessModel(model, new File(codePath + "/entity/" + className + ".java"), map);
//            templateProcessModel(dto, new File(codePath + "/dto/" + className + "DTO.java"), map);
//            templateProcessModel(controller, new File(codePath + "/controller/" + className + "Controller.java"), map);
//            templateProcessModel(service, new File(codePath + "/service/" + className + "Service.java"), map);
//            templateProcessModel(serviceImpl, new File(codePath + "/service/impl/" + className + "ServiceImpl.java"), map);
//            templateProcessModel(dao, new File(codePath + "/dao/" + className + "Dao.java"), map);
//            templateProcessModel(mapper, new File(xmlPath + "/" + className + "Mapper.xml"), map);

            log.info("---------------------代码生成完毕---------------------");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 生成模板
     *
     * @param model
     * @param file
     * @param map
     */
    private void templateProcessModel(Template model, File file, Map<String, Object> map) {
        // 第六步：创建一个Writer对象，一般创建一FileWriter对象，指定生成的文件名
        // 第七步：调用模板对象的process方法输出文件
        // 第八步：关闭流。
        try (Writer modelOut = new FileWriter(file)) {
            model.process(map, modelOut);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 将字符串的首字母转大写
     *
     * @param str 需要转换的字符串
     * @return String
     */
    private static String largerFirstChar(String str) {
        // 进行字母的ascii编码前移，效率要高于截取字符串进行转换的操作
        return Optional.ofNullable(str).map(s -> {
            char[] chars = s.toCharArray();
            chars[0] -= 32;
            return String.valueOf(chars);
        }).orElse("");
    }

    /**
     * 将字符串的首字母转小写
     *
     * @param str 需要转换的字符串
     * @return String
     */
    private static String lowerFirstChar(String str) {
        // 进行字母的ascii编码前移，效率要高于截取字符串进行转换的操作
        if (Character.isLowerCase(str.charAt(0))) {
            return str;
        }
        return Optional.of(str).map(s -> {
            char[] chars = s.toCharArray();
            chars[0] += 32;
            return String.valueOf(chars);
        }).orElse("");
    }

    /**
     * 判断字符串大小写
     *
     * @param word
     * @return boolean
     */
    public static boolean isAcronym(String word) {
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (Character.isLowerCase(c)) {
                return false;
            }
        }
        return true;
    }


    public static void main(String[] args) throws IOException {
        new CodeGenerator().generator("ability_evaluation", "创新能力评价排行榜信息");
        new CodeGenerator().generator("ability_evaluation_template", "创新能力评价模板信息");
        new CodeGenerator().generator("ability_evaluation_template_cardinality", "创新能力评价模板指标基数信息");
        new CodeGenerator().generator("ability_evaluation_template_comment", "创新能力评价模板评价文案信息");
        new CodeGenerator().generator("ability_evaluation_template_index", "创新能力评价模板指标信息");
//        new CodeGenerator().generator("up_task_general_fee_project", "依申请事项要素规范-收费项目信息");
//        new CodeGenerator().generator("up_task_general_material", "依申请事项要素规范-材料目录信息");
//        new CodeGenerator().generator("up_task_general_questions", "依申请事项要素规范-常见问题解答");
//        new CodeGenerator().generator("up_task_public_basic", "公共服务事项要素规范-基本信息");
//        new CodeGenerator().generator("up_task_public_extend", "公共服务事项要素规范-基本扩展信息");
//        new CodeGenerator().generator("up_task_public_fee_project", "公共服务事项要素规范-收费项目信息");
//        new CodeGenerator().generator("up_task_public_material", "公共服务事项要素规范-材料目录信息");
//        new CodeGenerator().generator("up_task_public_questions", "公共服务事项要素规范-常见问题解答");
    }
}
